package one.rewind.xforce.hsl;

import com.google.common.util.concurrent.*;
import io.netty.handler.codec.http.HttpMethod;
import one.rewind.nio.http.ReqObj;
import one.rewind.nio.http.Requester;
import one.rewind.txt.DateFormatUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 *
 */
public class HSLRequester implements Runnable {

    public static final Logger logger = LogManager.getLogger(HSLRequester.class.getName());

    public static DateTimeFormatter df = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");

    // 任务列表刷新间隔
    static int List_Refresh_Interval = 60000;

    // 任务请求次数
    static int Task_Request_Limit = 3;

    // 同ID任务请求延迟
    static int Task_Request_Delay = 1000;

    // 列表请求参数
    HSLRequester.RequestParams listRequestParams;

    // 列表中任务信息截取正则
    String listReg;

    // 列表中任务信息截取数量
    int listTaskLimit;

    // 任务请求参数
    HSLRequester.RequestParams taskRequestParams;

    // 任务成功识别正则
    String taskSuccessReg;

    // 任务失败识别正则
    String taskFailedReg;

    // 任务成功记录
    Map<String, Boolean> tasks_success = new ConcurrentHashMap<>();

    // 任务futures
    Map<String, List<ListenableFuture<Boolean>>> task_futures = new ConcurrentHashMap<>();

    // 调度执行服务
    ScheduledExecutorService ses = new ScheduledThreadPoolExecutor(40, new ThreadFactoryBuilder()
            .setNameFormat("HSL-ses-%d")
            .build());

    // 线程池
    ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryBuilder()
            .setNameFormat("HSL-exec-%d")
            .build());

    // 可监听的执行器服务
    ListeningExecutorService es = MoreExecutors.listeningDecorator(ses);

    /**
     * 构造方法
     * @param listRequestParams 列表请求参数
     * @param listReg  列表中任务信息截取正则
     * @param listTaskLimit 列表中任务信息截取数量
     * @param taskRequestParams 任务请求参数
     * @param taskSuccessReg 任务成功识别正则
     * @param taskFailedReg 任务失败识别正则
     */
    public HSLRequester(HSLRequester.RequestParams listRequestParams, String listReg, int listTaskLimit, HSLRequester.RequestParams taskRequestParams, String taskSuccessReg, String taskFailedReg) {

        this.listRequestParams = listRequestParams;
        this.listReg = listReg;
        this.listTaskLimit = listTaskLimit;
        this.taskRequestParams = taskRequestParams;
        this.taskSuccessReg = taskSuccessReg;
        this.taskFailedReg = taskFailedReg;
    }

    @Override
    public void run() {

        ses.scheduleAtFixedRate(() -> {

            try {
                // 获取任务id 和 发布时间
                List<Map<String, String>> taskInfo = getTaskInfo();

                logger.info("Task count: {}", taskInfo.size());

                // 任务处理
                taskInfo.forEach(item -> {

                    String id = item.get("id");

                    // 生成URL
                    String url = taskRequestParams.url();
                    for(String k : item.keySet()) {
                        url = url.replace("{{" + k + "}}", item.get(k));
                    }

                    // TODO 替换成实际的任务发布时间 - 5m
                    Date expect_start_time = DateFormatUtil.parseTime(item.get("time"));
                    String expect_start_time_str = df.print(new Date().getTime() + 4000);

                    // 生成并提交任务
                    Task t = new Task(id, url, expect_start_time_str);
                    submit(t);
                });
            }
            catch (IOException e) {
                logger.error("Refresh list error, ", e);
            }

        }, 0, List_Refresh_Interval, TimeUnit.MILLISECONDS);
    }


    /**
     * 从列表接口获取任务信息
     * @return 任务信息列表
     * @throws IOException 接口请求出错
     */
    public List<Map<String, String>> getTaskInfo() throws IOException {

        List<Map<String, String>> taskInfo = new LinkedList<>();

        ReqObj r = new Requester().req(
                listRequestParams.url(),
                listRequestParams.method(),
                listRequestParams.headers(),
                listRequestParams.cookie(),
                null,
                listRequestParams.payload()
            ).orElseThrow(IOException::new);

        Pattern p = Pattern.compile(listReg);
        Matcher m = p.matcher(r.getText());

        // 正则匹配
        while (m.find()) {

            taskInfo.add(
                m.namedGroups().keySet().stream().map(k -> new AbstractMap.SimpleEntry<>(k, m.group(k)))
                    .collect(Collectors.toMap(
                        AbstractMap.SimpleEntry::getKey,
                        AbstractMap.SimpleEntry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new)
                    )
            );

            if(taskInfo.size() >= listTaskLimit) break;
        }

        return taskInfo;
    }


    /**
     * 执行任务和子任务
     * @param t 任务
     */
    public void submit(Task t) {

        long ts = System.currentTimeMillis();
        long delay = t.expect_start_time.getTime() - ts;

        // 过期任务不执行
        if(delay <= 0) return;

        ses.schedule(() -> submit0(t), delay, TimeUnit.MILLISECONDS);

        IntStream.range(1, Task_Request_Limit + 1).boxed().forEach(i -> {
            Task st = t.genSubTask(i);
            ses.schedule(() -> submit0(st), st.expect_start_time.getTime() - ts, TimeUnit.MILLISECONDS);
        });
    }

    /**
     * 执行单个任务
     * @param t 任务
     */
    private void submit0(Task t) {

        // 若已经完成不再执行任务
        if(tasks_success.computeIfAbsent(t.o_id, v -> false)) return;

        // 生成并保存future
        ListenableFuture<Boolean> f = es.submit(t);
        task_futures.computeIfAbsent(t.o_id, v -> new LinkedList<>()).add(f);

        // 监听执行结果
        Futures.addCallback(f,
                new FutureCallback<>() {
                    public void onSuccess(Boolean success) {
                        logger.info("{} --> {}", t, success);
                        tasks_success.put(t.o_id, success);

                        // Cancel已经执行中的并发任务
                        task_futures.get(t.o_id).forEach(f_ -> f_.cancel(true));
                    }
                    public void onFailure(Throwable e) {
                        logger.error("[{}] Error request, ", t.id, e);
                    }
                },
                executor
        );
    }


    /**
     * 任务纪录类
     */
    public class Task implements Callable<Boolean>, Serializable {

        // id
        public String id;

        // 原始id
        public String o_id;

        // 请求URL
        public String url;

        // 预期执行时间
        public Date expect_start_time;

        // 创建时间
        public Date create_time = new Date();

        // 实际开始时间
        public Date start_time;

        // 实际执行时间
        public Date done_time;

        /**
         * 构造放啊发
         * @param id id
         * @param url url
         * @param expect_start_time_str 期待执行时间字符串 yyyy-MM-dd HH:mm:ss.SSS
         */
        public Task(String id, String url, String expect_start_time_str) {
            this.id = id;
            this.o_id = id;
            this.url = url;
            this.expect_start_time = df.parseDateTime(expect_start_time_str).toDate();
        }

        /**
         * 生成重试子任务
         * @param i 数量
         * @return 子任务
         */
        public Task genSubTask(int i) {
            Task st = new Task(id + "-sub-" + i, url, df.print(expect_start_time.getTime() + (long) i * Task_Request_Delay));
            st.o_id = id;
            return st;
        }

        /**
         *
         * @return 执行结果
         * @throws ContentMissMatchException 内容识别正则表达式失效
         * @throws IOException 任务请求失败
         */
        @Override
        public Boolean call() throws ContentMissMatchException, IOException {

            start_time = new Date();

            ReqObj r = new Requester().req(
                    url,
                    taskRequestParams.method(),
                    taskRequestParams.headers(),
                    taskRequestParams.cookie(),
                    null,
                    taskRequestParams.payload()
                    ).orElseThrow(IOException::new);

            done_time = new Date();

            // 识别成功请求
            Pattern pattern_success = Pattern.compile(taskSuccessReg);
            Matcher matcher = pattern_success.matcher(r.getText());
            if(matcher.find()) {
                return true;
            }

            // 识别失败请求
            Pattern pattern_failed = Pattern.compile(taskFailedReg);
            matcher = pattern_failed.matcher(r.getText());
            if(matcher.find()) {
                return false;
            }

            throw new ContentMissMatchException();
        }

        @Override
        public String toString() {
            return "[" + id + "] c:" + df.print(start_time.getTime()) + " w:" + (start_time.getTime() - create_time.getTime()) + " d:" + (done_time.getTime() - start_time.getTime());
        }
    }

    /**
     * RequestParams
     * @param method 请求方法
     * @param url URL/URL模板
     * @param headers 请求头信息
     * @param cookie Cookie
     * @param payload 请求负载
     */
    public record RequestParams(HttpMethod method, String url, Map<String, String> headers, String cookie, byte[] payload){}

    /**
     *
     */
    public static class ContentMissMatchException extends Exception {
        public ContentMissMatchException(){}
    }
}